#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iostream>

#include "network.hpp"

using namespace std;

#define BACKLOG 20     // how many pending connections queue will hold (for server)

SocketSet::SocketSet()
{
    clear();
}

SocketSet::~SocketSet()
{
    clear();
}

void SocketSet::clear()
{
    FD_ZERO(&master);
    FD_ZERO(&read_fds);
}

void SocketSet::add(int sockfd)
{
    if (fdmax < sockfd);
    fdmax = sockfd;
    FD_SET(sockfd, &master);
}

void SocketSet::remove(int sockfd)
{
    FD_CLR(sockfd, &master);
}

bool SocketSet::check(int sockfd)
{
    return FD_ISSET(sockfd, &read_fds);
}

void SocketSet::scan()
{
    read_fds = master;
    select(fdmax + 1, &read_fds, NULL, NULL, NULL);
}

void SocketSet::scan(double time)
{
    struct timeval tv;
    tv.tv_sec = int(floor(time));
    tv.tv_usec = int(time - floor(time));
    read_fds = master;
    select(fdmax + 1, &read_fds, NULL, NULL, &tv);
}

int start(char* port)
{
    struct addrinfo hints, *res;
    int serverfd;
    int yes=1;

    // first, load up address structs with getaddrinfo():

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

    int status;
    if ((status = getaddrinfo(NULL, port, &hints, &res)) != 0)
    {
        cerr << "Error: getaddrinfo: " << gai_strerror(status) << endl;
        return -1;
    }

    // make a socket, bind it, and listen on it:
    serverfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (serverfd < 0)
    {
        cerr << "Error: socket" << endl;
        return -1;
    }

    if (setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0)
    {
        close(serverfd);
        cerr << "Error: setsockopt" << endl;
        return -1;
    }

    if (bind(serverfd, res->ai_addr, res->ai_addrlen) < 0)
    {
        close(serverfd);
        cerr << "Error: bind (port already in use?)" << endl;
        return -1;
    }

    if (listen(serverfd, BACKLOG) < 0)
    {
        close(serverfd);
        cerr << "Error: listen" << endl;
        return -1;
    }

    freeaddrinfo(res);
    return serverfd;
}

int connect(char* site, char* port)
{
    struct addrinfo hints, *res, *p;
    int sockfd;

    // first, load up address structs with getaddrinfo():

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    int status = getaddrinfo(site, port, &hints, &res);
    if (status != 0)
    {
        cerr << "Error: getaddrinfo: " << gai_strerror(status) << endl;
        return -1;
    }

    // make a socket:
    for (p = res; p != NULL; p = p->ai_next)
    {
        sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (sockfd < 0)
        {
            continue;
        }

        // connect!

        if (connect(sockfd, p->ai_addr, p->ai_addrlen) < 0)
        {
            close(sockfd);
            continue;
        }
        break;
    }

    if (p == NULL)
    {
        cerr << "Error: connect" << endl;
        return -1;
    }

    return sockfd;
}

int accept(int serverfd)
{
    int new_fd;
    struct sockaddr_storage their_addr;
    socklen_t addr_size = sizeof their_addr;

    new_fd = accept(serverfd, (struct sockaddr *)&their_addr, &addr_size);
    if (new_fd == -1)
    {
        cerr << "Error: accept" << endl;
        return -1;
    }
    return new_fd;
}

int send(const char* msg, int length, int sockfd)
{
    int bytesSent = 0, total = 0;
    while (total < length)
    {
        bytesSent = send(sockfd, msg + total, length - total, 0);
        if (bytesSent == -1)
        {
            return -1;
        }
        total += bytesSent;
    }
    return total;
}

int send(string msg, int sockfd)
{
    return send(msg.c_str(), msg.length(), sockfd);
}

int recieve(char* msg, int length, int sockfd)
{
    int numbytes;
    numbytes = recv(sockfd, msg, length - 1, 0);
    if (numbytes == -1)
    {
        cerr << "Error: recv" << endl;
    }
    msg[numbytes] = '\0';

    return numbytes;
}

